#include "common/pch.h"

#include <format>

#include <cftc/reflect/class.h>
#include <cftc/reflect/field.h>
#include <cftc/reflect/annotation.h>
#include <cftc/util/strutils.h>
#include <cftf/loger/syslog.h>
#include <cftf/mysql/dbpool.h>
#include <cftf/meta/loader.h>

#include "dbc/structure.h"
#include "dbc/dbcmgr.h"
#include "dbc/loader.h"
#include "metadata/metamgr.h"

namespace dbc
{

    class LoaderHelper
    {
    public:
        LoaderHelper(Loader* loader) : loader_(loader)
        {
            if (!IsEof()) {
                record_ = loader_->GetRecord(curr_rows_idx_);
            }
        }

        bool IsEof()
        {
            return curr_rows_idx_ >= loader_->GetNumRows();
        }

        void Next()
        {
            if (IsEof()) {
                abort();
            }
            ++curr_rows_idx_;
            if (IsEof()) {
                record_ = nullptr;
            }
            else {
                fmt_idx_ = 0;
                record_ = loader_->GetRecord(curr_rows_idx_);
            }
        }

        float readFloat()
        {        
            SkipIgnoreFmt();
            if ((DBCFieldFormat_e)loader_->GetFmt().at(fmt_idx_) == DBCFieldFormat_e::DBC_FF_FLOAT) {
                return record_->getFloat(fmt_idx_++);
            } else {
                abort();
            }
        }

        unsigned int readUInt()
        {
            SkipIgnoreFmt();
            if ((DBCFieldFormat_e)loader_->GetFmt().at(fmt_idx_) == DBCFieldFormat_e::DBC_FF_INT ||
                (DBCFieldFormat_e)loader_->GetFmt().at(fmt_idx_) == DBCFieldFormat_e::DBC_FF_IND) {
                return record_->getUInt(fmt_idx_++);
            }
            else {
                abort();
            }
        }

        unsigned char readUInt8()
        {
            SkipIgnoreFmt();
            if ((DBCFieldFormat_e)loader_->GetFmt().at(fmt_idx_) == DBCFieldFormat_e::DBC_FF_BYTE) {
                return record_->getUInt8(fmt_idx_++);
            }
            else {
                abort();
            }
        }

        const char* readString()
        {
            SkipIgnoreFmt();
            if ((DBCFieldFormat_e)loader_->GetFmt().at(fmt_idx_) == DBCFieldFormat_e::DBC_FF_STRING) {
                return record_->getString(fmt_idx_++);
            }
            else {
                abort();
            }
        }

    private:

        void SkipIgnoreFmt()
        {
            while (fmt_idx_ < loader_->GetFmt().size()) {
                switch ((DBCFieldFormat_e)loader_->GetFmt().at(fmt_idx_))
                {
                case DBCFieldFormat_e::DBC_FF_FLOAT:
                    return;
                    break;
                case DBCFieldFormat_e::DBC_FF_IND:
                case DBCFieldFormat_e::DBC_FF_INT:
                    return;
                    break;
                case DBCFieldFormat_e::DBC_FF_BYTE:
                    return;
                    break;
                case DBCFieldFormat_e::DBC_FF_STRING:
                    return;
                    break;
                case DBCFieldFormat_e::DBC_FF_LOGIC:
                    assert(false && "Attempted to load DBC files that do not have field types that match what is in the core. Check DBCfmt.h or your DBC files.");
                    break;
                case DBCFieldFormat_e::DBC_FF_NA:
                case DBCFieldFormat_e::DBC_FF_NA_BYTE:
                case DBCFieldFormat_e::DBC_FF_SORT:
                    break;
                default:
                    assert(false && "Unknown field format character in DBCfmt.h");
                    abort();
                    break;
                }
                ++fmt_idx_;
            }
            if (fmt_idx_ >= loader_->GetFmt().size()) {
                abort();
            }
        }

    private:
        unsigned int curr_rows_idx_ = 0;
        Loader* loader_ = nullptr;
        std::size_t fmt_idx_ = 0;
        std::shared_ptr<Loader::Record> record_;
    };

    void UnMarshal(cftc::reflect::Object* obj, LoaderHelper& loader_helper);
    bool UnMarshalField(LoaderHelper& loader_helper, void* data, const std::shared_ptr<cftc::reflect::Type>& type, const std::shared_ptr<cftc::reflect::Field> field)
    {
#pragma warning(push)
#pragma warning(error: 4062)
        if (type->IsPointer()) {
            type->TryInPlaceNew(data);
        }
        void* rawdata_ptr = type->GetRawDataPtr(data);
        switch (type->GetKind())
        {
        case A8ReflectTypeKind_e::kNone:
        {
            abort();
            break;
        }
        case A8ReflectTypeKind_e::kInt8:
        {
            //*(char*)rawdata_ptr = in->Get<char>();
            abort();
            break;
        }
        case A8ReflectTypeKind_e::kUInt8:
        {
            *(unsigned char*)rawdata_ptr = loader_helper.readUInt8();
            break;
        }
        case A8ReflectTypeKind_e::kInt16:
        {
            //*(short*)rawdata_ptr = in->Get<short>();
            abort();
            break;
        }
        case A8ReflectTypeKind_e::kUInt16:
        {
            //*(unsigned short*)rawdata_ptr = in->Get<unsigned short>();
            abort();
            break;
        }
        case A8ReflectTypeKind_e::kInt32:
        {
#if 1
            * (unsigned int*)rawdata_ptr = loader_helper.readUInt();
#endif
            break;
        }
        case A8ReflectTypeKind_e::kUInt32:
        {
            *(unsigned int*)rawdata_ptr = loader_helper.readUInt();
            break;
        }
        case A8ReflectTypeKind_e::kInt64:
        {
            //*(long long*)rawdata_ptr = in->Get<long long>();
            abort();
            break;
        }
        case A8ReflectTypeKind_e::kUInt64:
        {
#if 1
            int v1 = loader_helper.readUInt();
            int v2 = loader_helper.readUInt();
#endif
            break;
        }
        case A8ReflectTypeKind_e::kFloat:
        {
            *(float*)rawdata_ptr = loader_helper.readFloat();
            break;
        }
        case A8ReflectTypeKind_e::kDouble:
        {
            //*(double*)rawdata_ptr = in->Get<double>();
            abort();
            break;
        }
        case A8ReflectTypeKind_e::kString:
        {
            *(std::string*)rawdata_ptr = loader_helper.readString();
            break;
        }
        case A8ReflectTypeKind_e::kObject:
        {
            UnMarshal((cftc::reflect::Object*)rawdata_ptr, loader_helper);
            break;
        }
        case A8ReflectTypeKind_e::kXObject:
        {
            break;
        }
        case A8ReflectTypeKind_e::kVector:
        case A8ReflectTypeKind_e::kList:
        {
            auto wowdbc_anno = field->GetAnnotation("wow_dbc");
            auto element_size_constant = cftc::reflect::Type::GetConstant(wowdbc_anno->GetOption("element_size")->Get<std::string>());
            std::size_t element_size = wowdbc_anno->GetOption("element_size")->Get<std::size_t>();
            if (element_size_constant) {
                element_size = element_size_constant->Get<std::size_t>();
            }
            if (element_size <= 0) {
                abort();
            }
            for (std::size_t i = 0; i < element_size; ++i) {
                void* element = type->NewElement(rawdata_ptr, nullptr);
                UnMarshalField(loader_helper, element, type->GetElementType(), field);
            }
            break;
        }
        case A8ReflectTypeKind_e::kMap:
        {
            abort();
            break;
        }
        SWITCH_ENUM_CHECK()
        }
#pragma warning(pop)
        return true;
    }

    static void UnMarshal(cftc::reflect::Object* obj, LoaderHelper& loader_helper)
    {
        obj->GetClass()->TraverseFields(
            [obj, &loader_helper](const std::shared_ptr<cftc::reflect::Field> field) -> bool
            {
                UnMarshalField(loader_helper, (char*)obj + field->GetOffset(), field->GetType(), field);
                return true;
            }
        );
    }

    DbcMgr::DbcMgr()
    {
        loader_ = std::make_shared<cftf::meta::Loader>();
    }

    void DbcMgr::Init()
    {
        cftf::loger::SysLog::Instance().Info("DbcMgr.Init start");
        {
            loader_->RegMetaTable<SpellEntry>("Spell.dbc", "Id");
            loader_->RegMetaTable<MapEntry>("Map.dbc", "MapID");
            loader_->RegMetaTable<CreatureDisplayInfoEntry>("CreatureDisplayInfo.dbc", "Displayid");
            loader_->RegMetaTable<ChrRacesEntry>("ChrRaces.dbc", "RaceID");
            loader_->RegMetaTable<TaxiNodesEntry>("TaxiNodes.dbc", "ID");
            loader_->RegMetaTable<SkillLineEntry>("SkillLine.dbc", "id"); //
            loader_->RegMetaTable<ChrClassesEntry>("ChrClasses.dbc", "ClassID");
            loader_->RegMetaTable<TalentEntry>("Talent.dbc", "TalentID");
            loader_->RegMetaTable<SpellShapeshiftFormEntry>("SpellShapeshiftForm.dbc", "ID");
        }
        Load();
        cftf::loger::SysLog::Instance().Info("DbcMgr.Init end");
    }

    void DbcMgr::UnInit()
    {
    }

    void DbcMgr::Load()
    {
        loader_->Load(
            [](const std::shared_ptr<cftf::meta::TableConf> conf)
            {             
                cftf::loger::SysLog::Instance().Info(std::format("dbcmtr.load {} begin", conf->GetTableName()));
                auto wowdbc_anno = conf->GetTableMetaClass()->GetAnnotation("wow_dbc");
                if (!wowdbc_anno) {
                    abort();
                }
                if (!wowdbc_anno->HasOption("fmt")) {
                    abort();
                }
                std::string dbc_fmt = wowdbc_anno->GetOption("fmt")->Get<std::string>();
                auto fmt_const = cftc::reflect::Type::GetConstant(dbc_fmt);
                if (fmt_const) {
                    dbc_fmt = fmt_const->Get<std::string>();
                }
                auto dbc_loader = dbc::Loader();
                if (!dbc_loader.Load(std::format("{}/dbc/{}", mt::MetaMgr::Instance().GetDataDir(), conf->GetTableName()).c_str(), 
                    dbc_fmt.c_str())) {
                    abort();
                }
                LoaderHelper loader_helper(&dbc_loader);
                std::size_t readed_num = 0;
                while (!loader_helper.IsEof()) {
                    conf->UnMarshalAndAdd(
                        [&loader_helper, &readed_num](cftc::reflect::Object* obj) -> bool
                        {
                            UnMarshal(obj, loader_helper);
                            ++readed_num;
                            return true;
                        });
                    loader_helper.Next();
                }
                cftf::loger::SysLog::Instance().Info(std::format("dbcmtr.load {} {} end", conf->GetTableName(), readed_num));
            });
    }

}